package com.xg.mina.filter;

import com.xg.mina.MinaTcpClient;
import com.xg.mina.ShakeMessage;
import com.xg.mina.Utils;
import com.xg.mina.message.Constant;
import org.apache.commons.codec.binary.Hex;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.IoFilterAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.core.write.WriteRequest;

import java.util.Arrays;

public class ShakeFilter extends IoFilterAdapter {
    /**
     * {@inheritDoc}
     */
    @Override
    public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception {
        nextFilter.sessionOpened(session);

        ShakeMessage shakeMessage = new ShakeMessage();
        IoBuffer buffer = shakeMessage.createC0C1();

        byte[] c1 = Utils.bufferToBytes(buffer);
        shakeMessage.setData(c1);

        buffer.flip();

        session.setAttribute(MinaTcpClient.SEND_INDEX, 2);
        session.setAttribute(MinaTcpClient.SEND_MSG_2, c1);
        session.write(buffer);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
        //addFirst
        if (message instanceof IoBuffer) {
            IoBuffer msg = (IoBuffer) message;
            Integer ack_index = (Integer) session.getAttribute(MinaTcpClient.ACK_INDEX);
            if (ack_index == null || ack_index < 3) {
                msg.setAutoExpand(true);
            }

            IoBuffer buffer = (IoBuffer) session.getAttribute("buffer");
            if (buffer == null) {
                buffer = IoBuffer.allocate(1024);
                buffer.setAutoExpand(true);
                buffer.setAutoShrink(true);
                session.getAttribute("buffer", buffer);
            }

            if (ack_index == null) {
                //服务端未应答
                int remaining = msg.remaining();
                //大于1537字节，消息完整，收到C0C1
                if (remaining >= Constant.hand_shake_size + 1) {
                    System.out.println("收到S0S1");
                    //version
                    //position：1
                    //去掉版本号
                    msg.get();

                    byte[] s1 = new byte[Constant.hand_shake_size];
                    //position : 1 + 1536
                    msg.get(s1);

                    byte byte4 = s1[4];
                    if (byte4 == 0) {
                        //简单握手
                        System.out.println("简单握手");
                    } else {
                        valify(s1);
                    }
                    session.setAttribute(MinaTcpClient.ACK_INDEX, 2);

                    ShakeMessage shakeMessage = new ShakeMessage();
                    IoBuffer c2 = shakeMessage.createC2(s1);

                    Thread.sleep(2000);
                    byte[] c2Bytes = Utils.bufferToBytes(c2);
                    c2.flip();
                    session.write(c2);
                    session.setAttribute(MinaTcpClient.SEND_INDEX, 3);
                    session.setAttribute(MinaTcpClient.SEND_MSG_3, c2Bytes);

                    //大于 1537 字节，
                    remaining = msg.remaining();
                    if (remaining >= Constant.hand_shake_size) {
                        byte[] randomByte = new byte[Constant.hand_shake_size];
                        msg.get(randomByte);
                        System.out.println("收到s2" + Utils.byteToString(randomByte));
                        decoderS2(session, s1);
                        if (msg.hasRemaining()) {
                            nextFilter.messageReceived(session, msg);
                        }
                    } else {
                        if (remaining > 0) {
                            //缓存剩余字节
                            byte[] b = new byte[remaining];
                            msg.get(b);
                            buffer.put(b);
                        }
                    }
                }
            } else {
                if (ack_index == 2) {
                    //buffer不会为空
                    int remaining = msg.remaining();
                    byte[] b = new byte[remaining];
                    msg.get(b);
                    buffer.put(b);

                    int position = buffer.position();
                    if (position >= Constant.hand_shake_size) {
                        byte[] randomByte = Utils.bufferToBytes(buffer);
                        buffer.clear();
                        System.out.println("收到s2" + Utils.byteToString(randomByte));
                        decoderS2(session, randomByte);
                    }
                } else {
                    //直接给下一个过滤器
                    nextFilter.messageReceived(session, message);
                }
            }
        } else {
            nextFilter.messageReceived(session, message);
        }
    }

    private void valify(byte[] bytes) {
        //复杂握手
        int offset = Utils.calcOffset(bytes, 0);

        //1536-32
        byte[] join = Utils.digestJoin(bytes, offset);

        byte[] digest = new byte[32];
        //come_digest
        System.arraycopy(bytes, offset, digest, 0, 32);

        byte[] key = new byte[36];
        System.arraycopy(Constant.GENUINE_FMS_KEY, 0, key, 0, key.length);
        byte[] array = Utils.calcSha256(join, key);

        boolean equals = Arrays.equals(array, digest);
        if (!equals) {
            throw new RuntimeException("复杂握手验证不匹配");
        } else {
            System.out.println("复杂握手验证成功");
        }
    }

    private int getBufferSize(IoBuffer buffer) {
        return buffer.limit() - buffer.remaining();
    }

    private void decoderS2(IoSession session, byte[] randomByte) {
        byte[] c1_join = getC1Join(session);
        byte[] calc_digest = Utils.calcSha256(c1_join, Constant.GENUINE_FMS_KEY);

        byte[] join = Arrays.copyOfRange(randomByte, 0, randomByte.length - 32);
        byte[] array = Utils.calcSha256(join, calc_digest);

        byte[] digest = Arrays.copyOfRange(randomByte, randomByte.length - 32, randomByte.length);

        boolean equals = Arrays.equals(array, digest);
        if (!equals) {
            throw new RuntimeException("S2验证失败");
        } else {
            System.out.println("S2验证成功");
        }
        session.setAttribute(MinaTcpClient.ACK_INDEX, 3);

        MinaTcpClient.connection(session);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
        nextFilter.messageSent(session, writeRequest);
    }

    private byte[] getC1Join(IoSession ioSession) {
        byte[] c0c1 = (byte[]) ioSession.getAttribute(MinaTcpClient.SEND_MSG_2);
        byte[] c1 = Arrays.copyOfRange(c0c1, 1, Constant.hand_shake_size);
        int offset = Utils.calcOffset(c1, 0);
        byte[] digest = new byte[Constant.digest_length];
        System.arraycopy(c1, offset, digest, 0, Constant.digest_length);
        return digest;
    }

    public static byte[] bytes(IoBuffer ioBuffer) {
        byte[] c0c1 = new byte[ioBuffer.position()];
        System.arraycopy(ioBuffer.array(), 0, c0c1, 0, c0c1.length);
        return c0c1;
    }

    public static void main(String[] args) {
        IoBuffer c0c1Buffer = Constant.c1Buffer;
        byte[] c1 = bytes(c0c1Buffer);

        ShakeMessage shakeMessage = new ShakeMessage();
        shakeMessage.createC0C1(c1);

        byte[] c1_digest = shakeMessage.getC1_digest();
        System.out.println("c1 digest:" + Hex.encodeHexString(c1_digest));
        System.out.println(Hex.encodeHexString(c1_digest).equals(Constant.c1_digest));


        IoBuffer s1Buffer = Constant.s1Buffer;
        byte[] s1 = bytes(s1Buffer);


        IoBuffer c2Buffer = Constant.c2Buffer;
        byte[] c2 = bytes(c2Buffer);

        ShakeMessage message = new ShakeMessage();
        message.createC2(s1, c2);
        System.out.println("c2 calc digest:" + Hex.encodeHexString(message.getC2_calc_digest()));
        System.out.println("c2 digest:" + Hex.encodeHexString(message.getC2_digest()));

        System.out.println(Hex.encodeHexString(message.getC2_calc_digest()).equals(Constant.c2_calc_digest));
        System.out.println(Hex.encodeHexString(message.getC2_digest()).equals(Constant.c2_digest));

        byte[] c21 = message.getC2();
        System.out.println(Hex.encodeHexString(c21));
        System.out.println(Hex.encodeHexString(c21).equals(Constant.c2_str));
    }
}
